/*
 * Modelos.cpp
 *
 *  Created on: 23/06/2012
 *      Author: natuchis
 */

#include "Modelos.h"


Modelos::Modelos(CompresorAritmetico *arit, AdminDeContextos *admin) {

	this->ordenActual = 0;
	this->ordenMax = 5;
	this->vecModelos = new vector<Modelo*>;
	this->charsExcluidos = new set<char>;

	this->arit = arit;
	this->admin = admin;

	this->modeloMenos1 = new ModeloMenos1();

	this->vecModelos->push_back(new Modelo0());
	for (unsigned short int i = 0; i < this->ordenMax; i++) {
		this->vecModelos->push_back(new ModeloX());
	}
}

Modelos::~Modelos() {

	for (unsigned short int i = 0; i< this->vecModelos->size(); i++) {
		delete this->vecModelos->at(i);
	}
	delete this->vecModelos;
	delete this->charsExcluidos;
	delete this->modeloMenos1;
}

void Modelos::setOrdenMaximo(unsigned short int ordenMax) {
	this->ordenMax = ordenMax;
}

void Modelos::incrementarOrden() {

	if (this->ordenActual < this->ordenMax) {
		this->ordenActual++;
	}
}

void Modelos::descomprimir() {

//	cout << "Orden Maximo: " << this->ordenMax << endl;

	FILE* arch;

	string ruta = this->arit->getRuta();

	arch = fopen(ruta.c_str(),"a+b");

	Datos datos;
	map<string,Caracter> mapa;

	mapa.clear();
	this->vaciarExcluidos();

	/* Comienzo en el modelo-1 ya que el 1er caracter no existe en el modelo0 y por ende
	 * se comprimio con ESC frecuencia = 1
	 */

	this->modeloMenos1->getMapa("", mapa, this->charsExcluidos);
	datos.setCaracteres(mapa);

	unsigned long int frec = this->modeloMenos1->getFrecuenciaTotal("");
	datos.setFrecTotal(frec);

	string caracter = this->arit->descomprimirCaracter(datos);

//	cout << "Caracter " << caracter << endl;

	if (caracter != "EOF") {

		fputc(caracter[0],arch);
		this->vecModelos->at(0)->actualizarModeloYExcluidos("",caracter[0],this->charsExcluidos);

		//Agrega el caracter en el modelo 1.
		this->agregarContextoEnModeloSiguiente("",caracter[0]);

		this->admin->agregarCaracterACtx(caracter[0]);

		this->vaciarExcluidos();
		mapa.clear();

		Modelo *model;
		string contexto;
		bool termino;
		unsigned long int frecEsc;
		int ordenAlcanzado = 1;

		while (caracter != "EOF") {
			this->ordenActual = ordenAlcanzado;
			termino = false;
//			cout << "Orden Actual despues de asignar ordenAlcanzado " << this->ordenActual << endl;

			while (!termino) {
//				cout << "Despues de !termino... Orden Actual: " << this->ordenActual << endl;
				if (this->ordenActual >= 0) {
					contexto = this->admin->getContexto(this->ordenActual);
					model = this->vecModelos->at(this->ordenActual);

//					cout << "Contexto: " << contexto << endl;
//					cout << "Orden Actual: " << this->ordenActual << endl;
//					cout << "EXCLUIIIIIIIIIIIIIIIIIIIIIIIDOOOOOOOOOOOOSSSSSSS" << endl;
//
//					set<char>::iterator it;
//					for (it = this->charsExcluidos->begin(); it != this->charsExcluidos->end(); it++) {
//						cout << (*it) << endl;
//					}

					model->getMapa(contexto,mapa,this->charsExcluidos);
					frecEsc = model->getFrecuenciaEsc(contexto) - model->getCantExcluidosPresentesEnModelo(contexto,this->charsExcluidos);

					// Caso especial, si el contexto tiene un solo caracter y se encuentra excluido.
					if (frecEsc == 0) {
						frecEsc = 1;
					}

					mapa.insert(pair<string,Caracter>("ESC",Caracter(frecEsc)));
					datos.setCaracteres(mapa);

//					cout << "MAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" << endl;
//					map<string,Caracter>::iterator its;
//					for (its = mapa.begin(); its != mapa.end(); its++) {
//						cout << "Caracter: " << its->first << " Frec: " << its->second.getFrecuencia() << endl;
//					}

					frec = model->getFrecuenciaTotal(contexto) - model->getFrecuenciaTotalExcluidos(contexto, this->charsExcluidos) + frecEsc;
					datos.setFrecTotal(frec);

//					cout << "Frecuencia Total: " << frec << endl;

					caracter = this->arit->descomprimirCaracter(datos);

//					cout << "Caracter descomprimido " << caracter << endl;

					if (caracter != "ESC") {
//						model->actualizarModeloYExcluidos(contexto,caracter[0],this->charsExcluidos);
						this->actualizarModelosSiguientes(caracter[0],ordenAlcanzado);
						this->admin->agregarCaracterACtx(caracter[0]);
						ordenAlcanzado++;
						if (ordenAlcanzado > this->ordenMax) {
							this->ordenActual = this->ordenMax;
						}
						else {
							this->ordenActual = ordenAlcanzado;
						}
						fputc (caracter[0],arch);
						this->vaciarExcluidos();
						termino = false; //no llegue al EOF
					}
					else { //caracter == "ESC"
						model->setExcluidos(contexto, this->charsExcluidos);
						this->decrementarOrdenActual();
						termino = false; //no llegue al EOF
					}
					mapa.clear();
				} //fin ModeloX

				else { //Modelo-1

					contexto = "";
					model = this->modeloMenos1;

//					cout << "Contexto: " << contexto << endl;
//					cout << "Orden Actual: " << this->ordenActual << endl;
//					cout << "EXCLUIIIIIIIIIIIIIIIIIIIIIIIDOOOOOOOOOOOOSSSSSSS" << endl;

//					set<char>::iterator it;
//					for (it = this->charsExcluidos->begin(); it != this->charsExcluidos->end(); it++) {
//						cout << (*it) << endl;
//					}

					model->getMapa(contexto,mapa,this->charsExcluidos);
					datos.setCaracteres(mapa);

//					cout << "MAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA en descomprimir de modelos" << endl;
//					map<string,Caracter>::iterator its;
//					for (its = mapa.begin(); its != mapa.end(); its++) {
//						cout << "Caracter: " << its->first << " Frec: " << its->second.getFrecuencia() << endl;
//					}

					frec = model->getFrecuenciaTotal(contexto) - model->getFrecuenciaTotalExcluidos(contexto, this->charsExcluidos);
					datos.setFrecTotal(frec);

//					cout << "Frecuencia Total: " << frec << endl;

					caracter = this->arit->descomprimirCaracter(datos);

//					cout << "Caracter descomprimido " << caracter << endl;

					if (caracter != "EOF") {
//						model->actualizarModeloYExcluidos(contexto,caracter[0],this->charsExcluidos);
						this->actualizarModelosSiguientes(caracter[0],ordenAlcanzado);
						this->admin->agregarCaracterACtx(caracter[0]);
						ordenAlcanzado++;
						if (ordenAlcanzado > this->ordenMax) {
							this->ordenActual = this->ordenMax;
						}
						else {
							this->ordenActual = ordenAlcanzado;
						}
						fputc (caracter[0],arch);
						this->vaciarExcluidos();
						termino = false; //no llegue al EOF
					}
					else { //caracter == "EOF"
//						arch << '\n';
						termino = true;
					}
					mapa.clear();
				} //fin else Modelo-1
			} //fin while (!termino)
		} //fin while (caracter != "EOF")
	} //fin if(caracter!="EOF)
	fclose(arch);
}

void Modelos::actualizarModelosSiguientes(char carac, int orden) {

	if (orden >= this->ordenMax) {
		orden = this->ordenMax;
	}

	while (orden >= this->ordenActual && orden >= 0) {
		this->vecModelos->at(orden)->actualizar(this->admin->getContexto(orden),carac);
		if (orden < this->ordenMax) {
			this->agregarContextoEnModeloSiguiente(this->admin->getContexto(orden),carac);
		}
//		this->actualizarModeloSiguienteParaDescomprimir(this->admin->getContexto(orden),carac);
		orden--;
	}
//	if (this->ordenActual == -1) {
//		this->vecModelos->at(0)->actualizar("",carac);
//	}
}

//void Modelos::actualizarModeloSiguienteParaDescomprimir(string contexto, char carac) {
//
//	size_t orden = contexto.size();
//	string ctx = contexto.append(1,carac);
//
//	if (orden < this->ordenMax && orden >= 0) {
//
//		this->vecModelos->at(ctx.size())->actualizar(ctx,carac);
//	}
//}

void Modelos::decrementarOrdenActual() {

	this->ordenActual--;
}

void Modelos::comprimir(string contexto, char carac, bool estoyEnElEof) {

	this->ordenActual = contexto.size();

	string ctx = contexto;
	Datos datos;

	bool termino = false;
	bool existe = false;

	if (!estoyEnElEof) {
		while (!termino) {
			if (this->ordenActual >= 0) {
				existe = this->existeCaracterEnEsteContextoEnOrdenActual(ctx,carac);
				this->vecModelos->at(this->ordenActual)->comprimir(ctx,carac,this->charsExcluidos,datos,estoyEnElEof);

//				cout << "Entre ModeloX sin estar en el EOF!!!" << endl;
//
//				cout << "Orden actual " << this->ordenActual << endl;
//				cout << "Existe " << carac << " en el contexto " << ctx << " " << existe << endl;
//				cout << "Caracter a comp " << datos.getCaracAComp() << endl;
//				cout << " con frecuencia total " << datos.getFrecTotal() << endl;
//
//				map<string,Caracter> mapa= datos.getCaracteres();
//				map<string,Caracter>::iterator it;
//
//				for (it = mapa.begin(); it != mapa.end(); it++) {
//					cout << "Caracter " << (*it).first << endl;
//					cout << "Frec " << (*it).second.getFrecuencia() << endl;
//				}

				this->vecModelos->at(this->ordenActual)->actualizarModeloYExcluidos(ctx,carac,this->charsExcluidos);
				this->agregarContextoEnModeloSiguiente(ctx,carac);


				//Llamar a compresorAritmetico y pasarle los datos
				this->arit->comprimirCaracter(datos);

			}
			else {
				existe = this->existeCaracterEnEsteContextoEnOrdenActual(ctx, carac);
				this->modeloMenos1->comprimir(ctx,carac,this->charsExcluidos,datos,estoyEnElEof);




//				cout << "Entre Modelo-1 sin estar en el EOF!!!" << endl;
//				cout << "Orden actual " << this->ordenActual << endl;
//				cout << "Existe " << carac << " en el contexto " << ctx << " " << existe << endl;
//				cout << "Caracter a comp " << datos.getCaracAComp() << " con frecuencia total " << datos.getFrecTotal() << endl;
//
//				map<string,Caracter> mapa= datos.getCaracteres();
//				map<string,Caracter>::iterator it;
//
//				for (it = mapa.begin(); it != mapa.end(); it++) {
//					cout << "Caracter " << (*it).first << endl;
//					cout << "Frec " << (*it).second.getFrecuencia() << endl;
//				}

				//Llamar a compresorAritmetico y pasarle los datos
				this->arit->comprimirCaracter(datos);
			}

			if (existe) {
				termino = true;
			}
			else {
				termino = false;
				this->decrementarOrdenActual();
				if (ctx.size() > 0) {
					ctx = ctx.erase(0,1);
				}
			}
		}
	}
	else {
		while (!termino) {
			if (this->ordenActual >= 0) {
				existe = false;
				this->vecModelos->at(this->ordenActual)->comprimir(ctx,carac,this->charsExcluidos,datos,estoyEnElEof);
				this->vecModelos->at(this->ordenActual)->setExcluidos(ctx,this->charsExcluidos);

//				cout << "Entre ModeloX y estoy en el EOF!!!" << endl;
//
//				cout << "Orden actual " << this->ordenActual << endl;
//				cout << "Existe " << carac << " en el contexto " << ctx << " " << existe << endl;
//				cout << "Caracter a comp " << datos.getCaracAComp() << endl;
//				cout << " con frecuencia total " << datos.getFrecTotal() << endl;
//
//				map<string,Caracter> mapa= datos.getCaracteres();
//				map<string,Caracter>::iterator it;
//
//				for (it = mapa.begin(); it != mapa.end(); it++) {
//					cout << "Caracter " << (*it).first << endl;
//					cout << "Frec " << (*it).second.getFrecuencia() << endl;
//				}
//
//				cout << "EXCLUIIIIIIIIIIIIIIIIIIIIIIDOOOOOOOOOOOOOOOOOOOSSSSSSSSSSSS" << endl;
//				set<char>::iterator its;
//				for (its = this->charsExcluidos->begin(); its != this->charsExcluidos->end(); its++) {
//					cout << "Excluido " << (*its) << endl;
//				}


				//Llamar a compresorAritmetico y pasarle los datos
				this->arit->comprimirCaracter(datos);

			}
			else {
				existe = true;
				this->modeloMenos1->comprimir(ctx,carac,this->charsExcluidos,datos,estoyEnElEof);


//				cout << "Entre Modelo-1 y estoy en el EOF!!!" << endl;
//				cout << "Orden actual " << this->ordenActual << endl;
//				cout << "Existe " << carac << " en el contexto " << ctx << " " << existe << endl;
//				cout << "Caracter a comp " << datos.getCaracAComp() << " con frecuencia total " << datos.getFrecTotal() << endl;
//
//				map<string,Caracter> mapa= datos.getCaracteres();
//				map<string,Caracter>::iterator it;
//
//				for (it = mapa.begin(); it != mapa.end(); it++) {
//					cout << "Caracter " << (*it).first << endl;
//					cout << "Frec " << (*it).second.getFrecuencia() << endl;
//				}
//
//				set<char>::iterator its;
//				for (its = this->charsExcluidos->begin(); its != this->charsExcluidos->end(); its++) {
//					cout << "Excluido " << (*its) << endl;
//				}

				//Llamar a compresorAritmetico y pasarle los datos
				this->arit->comprimirCaracter(datos);
			}

			if (existe) {
				termino = true;
			}
			else {
				termino = false;
				this->decrementarOrdenActual();
				if (ctx.size() > 0) {
					ctx = ctx.erase(0,1);
				}
			}
		}

	}
}

bool Modelos::existeCaracterEnEsteContextoEnOrdenActual(string contexto, char carac) {

	size_t orden = contexto.size();
	bool existe;
	if (orden >= 0) {
		existe = this->vecModelos->at(orden)->existeCaracterEnEsteContexto(contexto,carac);
	}
	else {
		existe = true;
	}
	return existe;
}

void Modelos::agregarContextoEnModeloSiguiente(string contexto, char carac) {

	size_t orden = contexto.size();
	contexto += carac;

	if (orden < this->ordenMax && orden >= 0) {

		this->vecModelos->at(contexto.size())->agregarContexto(contexto);
	}
}

void Modelos::vaciarExcluidos() {

	this->charsExcluidos->clear();
}
